﻿// Copyright (c) 2014 Morten Bakkedal
// This code is published under the MIT License.

using System;
using System.Text;

using FuncLib.DualFunctions.Compilation;

namespace FuncLib.DualFunctions.Expressions
{
	internal abstract class Expression
	{
		public virtual void Prepare(FunctionBody body)
		{
		    // Do nothing unless overridden.
		}

		public abstract string GenerateCode();

		public bool IsZero()
		{
			return this is NumberExpression && ((NumberExpression)this).Value == 0.0;
		}

		public static Expression Exp(Expression e)
		{
			return new MethodCallExpression("Math.Exp", e);
		}

		public static Expression Log(Expression e)
		{
			return new MethodCallExpression("Math.Log", e);
		}

		public static Expression Sqrt(Expression e)
		{
			return new MethodCallExpression("Math.Sqrt", e);
		}

		public static Expression Pow(Expression e1, Expression e2)
		{
			return new MethodCallExpression("Math.Pow", e1, e2);
		}

		public static Expression Cos(Expression e)
		{
			return new MethodCallExpression("Math.Cos", e);
		}

		public static Expression Sin(Expression e)
		{
			return new MethodCallExpression("Math.Sin", e);
		}

		public static Expression MarkLocal(Expression e)
		{
			if (e is MarkLocalExpression)
			{
				// Already marked as local.
				return e;
			}

			if (e is NumberExpression)
			{
				// Skip for numbers.
				return e;
			}

			return new MarkLocalExpression(e);
		}

		public static implicit operator Expression(double a)
		{
			return new NumberExpression(a);
		}

		public static Expression operator +(Expression e1, Expression e2)
		{
			return new BinaryOperatorExpression("+", e1, e2);
		}

		public static Expression operator -(Expression e)
		{
			return new UnaryOperatorExpression("-", e);
		}

		public static Expression operator -(Expression e1, Expression e2)
		{
			return new BinaryOperatorExpression("-", e1, e2);
		}

		public static Expression operator *(Expression e1, Expression e2)
		{
			return new BinaryOperatorExpression("*", e1, e2);
		}

		public static Expression operator /(Expression e1, Expression e2)
		{
			return new BinaryOperatorExpression("/", e1, e2);
		}

		private class MarkLocalExpression : Expression
		{
			private Expression expression, localExpression;

			public MarkLocalExpression(Expression expression)
			{
				this.expression = expression;
			}

			public override void Prepare(FunctionBody body)
			{
				expression.Prepare(body);

				if (localExpression == null)
				{
					localExpression = body.AddLocal(expression);
				}
			}

			public override string GenerateCode()
			{
				return localExpression.GenerateCode();
			}
		}

		private class UnaryOperatorExpression : Expression
		{
			private string operatorName;
			private Expression expression;

			public UnaryOperatorExpression(string operatorName, Expression expression)
			{
				this.operatorName = operatorName;
				this.expression = expression;
			}

			public override void Prepare(FunctionBody body)
			{
				expression.Prepare(body);
			}

			public override string GenerateCode()
			{
				return "(" + operatorName + expression.GenerateCode() + ")";
			}
		}

		private class BinaryOperatorExpression : Expression
		{
			private string operatorName;
			private Expression expression1, expression2;

			public BinaryOperatorExpression(string operatorName, Expression expression1, Expression expression2)
			{
				this.operatorName = operatorName;
				this.expression1 = expression1;
				this.expression2 = expression2;
			}

			public override void Prepare(FunctionBody body)
			{
				expression1.Prepare(body);
				expression2.Prepare(body);
			}

			public override string GenerateCode()
			{
				return "(" + expression1.GenerateCode() + operatorName + expression2.GenerateCode() + ")";
			}
		}

		private class MethodCallExpression : Expression
		{
			private string methodName;
			private Expression[] expressions;

			public MethodCallExpression(string methodName, params Expression[] expressions)
			{
				this.methodName = methodName;
				this.expressions = expressions;
			}

			public override void Prepare(FunctionBody body)
			{
				foreach (Expression expression in expressions)
				{
					expression.Prepare(body);
				}
			}

			public override string GenerateCode()
			{
				StringBuilder sb = new StringBuilder();
				sb.Append(methodName);
				sb.Append("(");
				for (int i = 0; i < expressions.Length; i++)
				{
					if (i > 0)
					{
						sb.Append(", ");
					}
					sb.Append(expressions[i].GenerateCode());
				}
				sb.Append(")");

				return sb.ToString();
			}
		}
	}
}
